#include "freyabaseextensionex.h"
#include "ftcpsocket.h"

#include <QtMath>
#include <QtConcurrent>

FreyaPluginPusherEx::FreyaPluginPusherEx(QString PluginID, QHostAddress addr, int port, FreyaBaseControl *pControl, QObject *parent) :
    QThread(parent), m_MsgAuth(0), m_CmdAuth(0), m_PluginID(PluginID), m_AddrPlugin(addr), m_IPortPlugin(port), m_pControl(pControl)
{
    qDebug() << "FreyaLib > " << "FreyaPluginPusherEx:ID:" << PluginID;

    setObjectName("PusherEx_" + PluginID.right(6));
    moveToThread(this);
    start();

    QMetaObject::invokeMethod(this, "OnConnect");
}

FreyaPluginPusherEx::~FreyaPluginPusherEx()
{
}

FreyaPluginPusherEx *FreyaPluginPusherEx::ConnectToPlugin(QString PluginID, QHostAddress addr, int port, FreyaBaseControl *pControl, QObject *parent)
{
    FreyaPluginPusherEx * pusher = new FreyaPluginPusherEx(PluginID, addr, port, pControl, parent);
    QThread::sleep(1);
    if(QTcpSocket::ConnectedState != pusher->m_Pusher->State())
    {
        pusher->deleteLater();
        pusher->m_Pusher->deleteLater();
        return NULL;
    }
    return pusher;
}

void FreyaPluginPusherEx::PusherExecute(const FreyaData pData)
{
    if(m_MsgAuth & pData->command)
    {
        qDebug() << "FreyaLib > " << "PusherExcute: Command:" << m_pControl->CmdLogString(pData->command) << "To:" << m_PluginID;
        QMetaObject::invokeMethod(this, "OnPusherExecute", Q_ARG(FreyaData, pData));
    }
}

QString &FreyaPluginPusherEx::FreyaPluginID()
{
    return m_PluginID;
}

bool FreyaPluginPusherEx::PeerAddress(QHostAddress &objAddress)
{
    objAddress = m_Pusher->PeerAddress();
    return true;
}

void FreyaPluginPusherEx::OnConnect()
{
    m_Pusher = new FTcpSocket();
    connect(m_Pusher, SIGNAL(StateChanged(QAbstractSocket::SocketState)), this, SLOT(OnStateChanged(QAbstractSocket::SocketState)));
    m_Pusher->ConnectToHost(m_AddrPlugin, m_IPortPlugin, QIODevice::ReadWrite);
    if(m_Pusher->WaitForConnected(1000))
    {
        qDebug() << "FreyaLib > " << "FreyaPluginPusher:ID:" << m_PluginID << "SUCCESS!";
        connect(m_Pusher, SIGNAL(SigPackData(QByteArray)), this, SLOT(OnReadyRead(QByteArray)));
        connect(m_Pusher, SIGNAL(Disconnected()), this, SIGNAL(ToDisconnected()));
        FreyaData pData = FreyaBaseData::CreateData(FREYALIB_CMD_CONNECTRESULT);
        OnPusherExecute(pData);
    }
}

void FreyaPluginPusherEx::OnPusherExecute(const FreyaData pData)
{
    QByteArray *pBa = new QByteArray();

#ifdef EXTTRANS_ASYNC
    SerializeTask *pSerializeTask = new SerializeTask(pBa, pData, true);
    connect(pSerializeTask, SIGNAL(toTaskFinished(QByteArray*, FreyaData, bool)), this, SLOT(OnPusherExecute(QByteArray*, FreyaData, bool)));
    QThreadPool::globalInstance()->start(pSerializeTask);
#else
    *pBa = pData->Serialize();
    OnPusherExecute(pBa, pData, true);
#endif
}

void FreyaPluginPusherEx::OnPusherExecute(QByteArray *pBa, FreyaData /*pData*/, bool pBRet)
{
    if(pBRet)
    {
        m_Pusher->Write(*pBa);
        m_Pusher->WaitForBytesWritten();
    }
    delete pBa;
    pBa = nullptr;
}

void FreyaPluginPusherEx::OnReadyRead(const QByteArray &byteData)
{
    FreyaData pData = FreyaBaseData::CreateData();
    QByteArray *pBa = new QByteArray(byteData);

#ifdef EXTTRANS_ASYNC
    SerializeTask *pUnserializeTask = new SerializeTask(pBa, pData, false);
    connect(pUnserializeTask, SIGNAL(toTaskFinished(QByteArray*, FreyaData, bool)), this, SLOT(OnReadyRead(QByteArray*, FreyaData, bool)));
    QThreadPool::globalInstance()->start(pUnserializeTask);
#else
    bool bRet = pData->Unserialize(*pBa);
    OnReadyRead(pBa, pData, bRet);
#endif
}

void FreyaPluginPusherEx::OnReadyRead(QByteArray *pBa, FreyaData pData, bool pBRet)
{
    if(nullptr != pBa)
    {
        delete pBa;
        pBa = nullptr;
    }

    if(!pBRet || pData.isNull())
    {
        qWarning() << "FreyaLib > " << "PusherReadyRead: cannot unserialize data";
        return;
    }

    switch (pData->command)
    {
    case FREYALIB_CMD_PLUGINAUTHREQUEST:
    {
        QVariantMap AuthCodeMap = pData->GetProperty().toMap();
        QStringList MsgAuthList = AuthCodeMap.value(FREYALIB_TYP_PLUGINMSGAUTH).toStringList();
        QStringList CmdAuthList = AuthCodeMap.value(FREYALIB_TYP_PLUGINCMDAUTH).toStringList();
        foreach (const QString &MsgCode, MsgAuthList)
        {
            FreyaData pCodeData = m_pControl->FindFreyaData(MsgCode);
            if(!pCodeData.isNull())
            {
                m_MsgAuth = (m_MsgAuth | pCodeData->GetProperty().toInt());
            }
        }
        qDebug() << "FreyaLib > " << "PluginID:" << m_PluginID << "MsgAuth:" << Qt::hex << m_MsgAuth;
        foreach (const QString &CmdCode, CmdAuthList)
        {
            FreyaData pCodeData = m_pControl->FindFreyaData(CmdCode);
            if(!pCodeData.isNull())
            {
                m_CmdAuth = (m_CmdAuth | pCodeData->GetProperty().toInt());
            }
        }
        FreyaData pAuthRepData = FreyaBaseData::CreateData(FREYALIB_CMD_PLUGINAUTHREPLAY);
        QVariantMap AuthMap;
        AuthMap.insert(FREYALIB_TYP_PLUGINMSGAUTH, m_MsgAuth);
        AuthMap.insert(FREYALIB_TYP_PLUGINCMDAUTH, m_CmdAuth);
        pAuthRepData->SetProperty(AuthMap);
        OnPusherExecute(pAuthRepData);
        qDebug() << "FreyaLib > " << "PluginID:" << m_PluginID << "CmdAuth:" << Qt::hex << m_CmdAuth;
    }
    break;
    case FREYALIB_CMD_PLUGINCHECK:
    {
        //        PusherExecute(FreyaBaseData::CreateData(FreyaLib_CMD_EXTCHECK));
    }
    break;
    default: // cmd request from plugin
    {
        if(m_CmdAuth & pData->command)
        {
            qDebug() << "FreyaLib > " << "Allow up command:" << m_pControl->CmdLogString(pData->command) << "From:" << m_PluginID;

            // 判断是否为宿主查询的回复 如果是，则进if，否则进else
            // 插入在Control中查询时
            FreyaData pReplyData = m_pControl->FindFreyaData(pData->dataID);
            if(!pReplyData.isNull())
            {
                pReplyData->DestoryArguments();
                pReplyData->SetProperty(pData->GetProperty());
                pReplyData->SetProperty(FREYALIB_FLG_ARGS, pData->GetProperty(FREYALIB_FLG_ARGS));
                if(!m_pControl->ReplyExecution(pReplyData, this))
                {
                    qWarning() << "FreyaLib > " << "Bad reply" << m_pControl->CmdLogString(pData->command) << "From:" << m_PluginID;
                }
            }
            else
            {
                emit ToPluginRequest(pData);
            }
        }
        else
        {
            qDebug() << "FreyaLib > " << "Not allow up command:" << m_pControl->CmdLogString(pData->command) << "From:" << m_PluginID;
        }
    }
    break;
    }
}

void FreyaPluginPusherEx::OnStateChanged(QAbstractSocket::SocketState state)
{
    switch(state)
    {
    case QTcpSocket::UnconnectedState:
    {
        qDebug() << "FreyaLib > " << "FreyaPluginPusherEx::OnStateChanged:" << "UnconnectedState";
    }
    break;

    case QTcpSocket::ConnectingState:
    {
        qDebug() << "FreyaLib > " << "FreyaPluginPusherEx::OnStateChanged:" << "ConnectingState";
    }
    break;

    case QTcpSocket::ConnectedState:
    {
        qDebug() << "FreyaLib > " << "FreyaPluginPusherEx::OnStateChanged:" << "ConnectedState";
    }
    break;

    case QTcpSocket::ClosingState:
    {
        qDebug() << "FreyaLib > " << "FreyaPluginPusherEx::OnStateChanged:" << "ClosingState";
    }
    break;
    default:
        break;
    }
}

///////////////

FreyaExtensionEx::FreyaExtensionEx(int port, FreyaBaseControl *pControl, const char *actionName) :
    QTcpServer(), FreyaBaseAction(pControl, actionName), m_pControl(pControl), m_isListening(false)
{
    m_isListening = listen(QHostAddress::Any, port);
    connect(this, &QTcpServer::newConnection, this, &FreyaExtensionEx::OnPluginRequest, Qt::QueuedConnection);
}

FreyaExtensionEx::~FreyaExtensionEx()
{
}

bool FreyaExtensionEx::DefineAuthCode(const QStringList &MsgAuth, const QStringList &CmdAuth)
{
    for(int i = 0; i < qMin(MsgAuth.size(), 4); ++i)
    {
        FreyaData pData = FreyaBaseData::CreateData();
        pData->dataID = MsgAuth.at(i);
        pData->SetProperty(qPow(2, i)); //0x01 0x02 0x04 0x08
        m_pControl->InsertFreyaData(pData);
    }
    for(int j = 0; j < qMin(CmdAuth.size(), 4); ++j)
    {
        FreyaData pData = FreyaBaseData::CreateData();
        pData->dataID = CmdAuth.at(j);
        pData->SetProperty(qPow(2, j) * 0x10); //0x10 0x20 0x40 0x80
        m_pControl->InsertFreyaData(pData);
    }
    return m_isListening;
}

QList<FreyaPluginPusherEx *> FreyaExtensionEx::PusherList()
{
    return m_PusherList;
}

int FreyaExtensionEx::PeepAddressList(QList<QHostAddress> &listAddress)
{
    listAddress.clear();

    foreach(FreyaPluginPusherEx * pPluginPusher , m_PusherList)
    {
        QHostAddress objAddree;
        if(pPluginPusher->PeerAddress(objAddree))
        {
            listAddress.append(objAddree);
        }
    }

    return listAddress.size();
}

void FreyaExtensionEx::Execute(const FreyaData pData)
{
    QMutexLocker locker(&m_PluginListMutex);

    //是否为私有消息，非插件消息
    if(pData->IsLocalData())
    {
        return;
    }

    if(!m_PusherList.isEmpty())
    {
        qDebug() << "FreyaLib > " << "Ext_Execution: DataID:" << pData->dataID
                 << "Command:" << m_pControl->CmdLogString(pData->command) /*<< "Property:" << pData->GetProperty()*/;
    }
    QString PluginId = pData->GetProperty(FREYALIB_FLG_PLUGINID).toString();

    QListIterator<FreyaPluginPusherEx*> PusherIT(m_PusherList);
    while(PusherIT.hasNext())
    {
        FreyaPluginPusherEx* pPusher = PusherIT.next();
        if(pPusher && PluginId != pPusher->FreyaPluginID())
        {
            pPusher->PusherExecute(pData);
        }
    }
}

void FreyaExtensionEx::OnPluginRequest()
{
    FTcpSocket *plugin = new FTcpSocket(nextPendingConnection());
    connect(plugin, SIGNAL(SigPackData(QByteArray)), this, SLOT(OnReadyRead(QByteArray)), Qt::QueuedConnection);
    //connect(plugin, SIGNAL(Disconnected()), this, SLOT(deleteLater())); //调用该指令会导致程序崩溃
}

void FreyaExtensionEx::OnReadyRead(const QByteArray &byteData)
{
    FTcpSocket *plugin = qobject_cast<FTcpSocket *>(sender());
    if (nullptr != plugin)
    {
        FreyaData pData = FreyaBaseData::CreateData();

        bool bRet = pData->Unserialize(byteData);

        if(false == bRet)
        {
            qWarning() << "FreyaLib >" << "FreyaExtensionEx cannot read request data !";
            return;
        }
        qDebug() << "FreyaLib > " << "FreyaExtensionEx recv request:" << m_pControl->CmdLogString(pData->command);
        if(FREYALIB_CMD_PLUGINREQUEST == pData->command)
        {
            FreyaData pResultData = FreyaBaseData::CreateData(FREYALIB_CMD_PLUGINRESULT);
            QString PluginID = "Freya_P_" + QUuid::createUuid().toString().remove("{").remove("}").remove("-");
            pResultData->SetProperty(PluginID);
            m_WaitPluginIDList.append(PluginID);
            plugin->Write(pResultData->Serialize());
            plugin->WaitForBytesWritten();
        }
        else if(FREYALIB_CMD_CONNECTREQUEST == pData->command)
        {
            QString PluginID = pData->GetProperty().toString();
            int PluginPort = pData->GetProperty(FREYALIB_FLG_PLUGINPORT).toInt();
            QHostAddress hostAddr = plugin->PeerAddress();
            qDebug() << "FreyaLib > " << "FreyaExtensionEx:" << m_pControl->CmdLogString(FREYALIB_CMD_CONNECTREQUEST) << PluginID << hostAddr << PluginPort;
            if(m_WaitPluginIDList.contains(PluginID))
            {
                m_WaitPluginIDList.removeOne(PluginID);
                FreyaPluginPusherEx* pPusher = FreyaPluginPusherEx::ConnectToPlugin(PluginID, hostAddr, PluginPort, m_pControl);
                if(pPusher)
                {
                    connect(pPusher, SIGNAL(ToPluginRequest(FreyaData)), this, SLOT(OnPusherRequest(FreyaData)), Qt::QueuedConnection);
                    connect(pPusher, SIGNAL(ToDisconnected()), this, SLOT(OnPusherDisconnected()), Qt::QueuedConnection);
                    m_PusherList.append(pPusher);

                    m_pControl->RequestExecution(FREYALIB_CMD_EXTHOSTCONNECTED);
                }
                else
                {
                    qWarning() << "FreyaLib > " << "FreyaExtensionEx: Cannot connect to plugin:" << PluginID;
                }
            }
            plugin->DisconnectFromHost();
        }
    }
}

void FreyaExtensionEx::OnPusherDisconnected()
{
    QMutexLocker locker(&m_PluginListMutex);
    FreyaPluginPusherEx* pPusher = qobject_cast<FreyaPluginPusherEx*>(sender());
    if(nullptr != pPusher && m_PusherList.removeOne(pPusher))
    {
        qDebug() << "FreyaLib > " << "PusherDisconnected:" << pPusher->FreyaPluginID();
        pPusher->quit();
        pPusher->wait();
        pPusher->deleteLater();
        m_pControl->RequestExecution(FREYALIB_CMD_EXTHOSTCUTED);
    }
}

void FreyaExtensionEx::OnPusherRequest(const FreyaData pData)
{
    int waitTime = pData->GetProperty(FREYALIB_FLG_WAITTIME).toInt();

    if(0 < waitTime)
    {
        auto ret = QtConcurrent::run(OnPusherQuery, pData, waitTime, sender(), m_pControl);
        Q_UNUSED(ret)
    }
    else
    {
        m_pControl->RequestExecution(pData, sender());
    }
}

void FreyaExtensionEx::OnPusherQuery(const FreyaData pData, int waitTime, QObject *sender, FreyaBaseControl *pControl)
{
    FreyaPluginPusherEx* pPusher = qobject_cast<FreyaPluginPusherEx*>(sender);
    // 异常（超时）则不回复，让插件能够获取超时状态
    if(pControl->RequestExecution(pData, sender, waitTime) && pPusher)
    {
        qDebug() << "FreyaLib > " << "Reply_Execution:" << "DataID:" << pData->dataID
                 << "Command:" << pControl->CmdLogString(pData->command) << "Property:" << pData->GetProperty();
        pPusher->PusherExecute(pData);
    }
    else
    {
        qWarning() << "FreyaLib > " << "Reply_Execution failed:" << "DataID:" << pData->dataID
                   << "Command:" << pControl->CmdLogString(pData->command) << "Property:" << pData->GetProperty();
    }
}
